package com.hbzhit.modules.act.factory;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hbzhit.common.entity.ProcessEntity;
import com.hbzhit.common.exception.RRException;
import com.hbzhit.common.utils.SerialNumUtils;
import com.hbzhit.common.utils.ShiroUtils;
import com.hbzhit.common.utils.SpringContextUtils;
import com.hbzhit.modules.act.dto.CommentDto;
import com.hbzhit.modules.act.dto.TaskDTO;
import com.hbzhit.modules.act.enums.ProcessActionEnum;
import com.hbzhit.modules.act.enums.ProcessStatusEnum;
import com.hbzhit.modules.act.handle.WorkFlow;
import com.hbzhit.modules.act.handle.WorkFlowQuery;
import com.hbzhit.modules.act.handle.WorkFlowTask;
import com.hbzhit.modules.act.handle.WorkFlowTaskQuery;
import com.hbzhit.modules.sys.entity.SysUserEntity;
import com.hbzhit.modules.sys.service.SysUserRoleService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程工厂
 * @author DING
 */
public final class WorkFlowFactory {
    private static final Logger log = LoggerFactory.getLogger(WorkFlowFactory.class);

    private static WorkFlow workFlowHandle;
    private static WorkFlowQuery workFlowQueryHandle;
    private static WorkFlowTask taskHandle;
    private static WorkFlowTaskQuery taskQueryHandle;

    private static SysUserRoleService sysUserRoleService;

    private static final String FLAG = "审批";

    static {
        WorkFlowFactory.workFlowHandle = (WorkFlow) SpringContextUtils.getBean("workFlowHandler");
        WorkFlowFactory.workFlowQueryHandle = (WorkFlowQuery) SpringContextUtils.getBean("workFlowQueryHandler");
        WorkFlowFactory.taskHandle = (WorkFlowTask) SpringContextUtils.getBean("workFlowTaskHandler");
        WorkFlowFactory.taskQueryHandle = (WorkFlowTaskQuery) SpringContextUtils.getBean("workFlowTaskQueryHandler");

        WorkFlowFactory.sysUserRoleService = (SysUserRoleService) SpringContextUtils.getBean("sysUserRoleService");
    }

    /**
     * 启动流程
     * @param service 业务service
     * @param process 业务流程对象
     * @param processDefKey 流程定义id
     * @return 流程实例
     */
    public static ProcessInstance start(IService service, ProcessEntity process, String processDefKey) {
        ProcessInstance pi;

        //数据校验
        if (StrUtil.isBlank(processDefKey)) {
            throw new RRException("启动流程出错：流程key不能为空");
        }
        if (process == null || process.getId() == null) {
            throw new RRException("启动流程出错：业务对象不能为空");
        }
        TaskDTO taskDTO = process.getTask();
        if (taskDTO == null) {
            throw new RRException("启动流程出错：任务对象不能为空");
        }
        try {
            // 业务id=流程定义id_业务表id
            String businessKey = String.valueOf(process.getId());
            // 启动流程
            pi = workFlowHandle.startProcessInstanceByKey(processDefKey, businessKey, taskDTO.getVariables());

            // 更新业务表流程id、流程编码、状态
            process.setProcessId(Long.parseLong(pi.getProcessInstanceId()));
            process.setStatus(ProcessStatusEnum.ACTIVE.getStatus());
            // 获取最大编码
            String nowDateStr = DateUtil.format(new Date(), "yyyyMMdd");
            ProcessEntity maxProcess = (ProcessEntity) service.getOne(new QueryWrapper<>()
                    .eq("status", ProcessStatusEnum.ACTIVE.getStatus())
                    .like("process_no", nowDateStr)
                    .orderByDesc("process_no"),
                    false
            );
            process.setProcessNo(SerialNumUtils.nextSimpleDateSerial(maxProcess == null ? null : maxProcess.getProcessNo()));

            service.updateById(process);
        } catch (Exception e) {
            throw new RRException("启动流程出错", e);
        }

        return pi;
    }

    /**
     * 启动并提交流程
     * @param service 业务service
     * @param process 业务流程对象
     * @param processDefKey 流程定义id
     */
    public static void startAndComplete(IService service, ProcessEntity process, String processDefKey) {
        // 当前用户
        SysUserEntity user = ShiroUtils.getUser();

        // 启动流程
        TaskDTO taskDTO = new TaskDTO();
        taskDTO.setAction(ProcessActionEnum.COMMIT.getValue());
        taskDTO.setActionName("提交");
        taskDTO.setOpinion("提交申请");
        Map<String, Object> outFlag = new HashMap<>();
        outFlag.put("action", ProcessActionEnum.COMMIT.getValue());
        outFlag.put("name", "提交");
        List<Map> outFlags = new ArrayList<>();
        outFlags.add(outFlag);
        taskDTO.setOutFlags(outFlags);
        process.setTask(taskDTO);

        ProcessInstance pi = start(service, process, processDefKey);
        try {
            //获取流程实例id
            String processInsId = pi.getProcessInstanceId();
            process.setProcessId(Long.parseLong(processInsId));
            //补充申请人、申请时间
            process.setApplicant(ObjectUtil.isEmpty(process.getApplicant()) ? user.getId() : process.getApplicant());
            process.setApplyTime(ObjectUtil.isEmpty(process.getApplyTime()) ? new Date() : process.getApplyTime());
            //获取任务id
            Task task = taskQueryHandle.processInstanceId(processInsId);
            //执行任务
            taskDTO.setTaskId(task.getId());
            process.setTask(taskDTO);

            doAction(service, process);
        } catch (Exception e) {
            throw new RRException("执行流程出错", e);
        }

    }

    /**
     * 执行流程
     * @param service 业务service
     * @param process 业务流程对象
     */
    public static void doAction(IService service, ProcessEntity process) {

        SysUserEntity user = ShiroUtils.getUser();

        // 参数校验取值
        //获取流程实例id
        if (process == null || process.getProcessId() == null) {
            throw new RRException("执行流程出错：业务对象及流程实例ID不能为空");
        }
        String processId = String.valueOf(process.getProcessId());

        //获取任务id
        TaskDTO taskDTO = process.getTask();
        if (taskDTO == null) {
            throw new RRException("执行流程出错：任务对象不能为空");
        }
        String taskId = taskDTO.getTaskId();

        // 获取执行动作标识
        if (taskDTO.getAction() == null) {
            throw new RRException("执行流程出错：动作标识不能为空");
        }
        int action = taskDTO.getAction();

        //获取动作名称
        if (CollectionUtil.isEmpty(taskDTO.getOutFlags())) {
            throw new RRException("执行流程出错：流程出口信息(OutFlags)不能为空");
        }
        String actionName = null;
        for (Map outFlag : taskDTO.getOutFlags()) {
            try {
                int flagAction = Integer.parseInt(outFlag.get("action").toString());
                if (action == flagAction) {
                    actionName = String.valueOf(outFlag.get("name"));
                    break;
                }
            } catch (Exception e) {
                throw new RRException("执行流程出错：流程出口信息(OutFlags)数据有误");
            }
        }
        if (StrUtil.isBlank(actionName)) {
            throw new RRException("执行流程出错：流程出口信息(OutFlags)中没有包含当前动作");
        }

        Map<String, Object> variables = taskDTO.getVariables();
        if (variables == null) {
            throw new RRException("执行流程出错：条件参数不能为空");
        }
        String userId;
        try {
            userId = String.valueOf(user.getId());
        } catch (Exception e) {
            throw new RRException("执行流程出错：获取当前用户失败");
        }

        //设置流程发起人
        workFlowHandle.setAuthenticatedUserId(userId);

        // 添加备注信息
        CommentDto commentDto = new CommentDto();
        commentDto.setAction(action);
        commentDto.setActionName(actionName);
        commentDto.setMessage(StrUtil.isBlank(taskDTO.getOpinion()) ? actionName : taskDTO.getOpinion());
        taskHandle.addComment(taskDTO.getTaskId(), processId, JSONObject.toJSONString(commentDto));

        // 根据执行动作执行流程
        process.setStatus(ProcessStatusEnum.ACTIVE.getStatus());
        process.setStatusName(actionName);
        process.setApprover(user.getId());
        process.setApprovalTime(new Date());

        //提交
        if (action == ProcessActionEnum.COMMIT.getValue()) {
            process.setApprover(null);
            process.setApprovalTime(null);
        }

        //指定
        if (action == ProcessActionEnum.DISPATCH.getValue()) {
            // TODO 判断是否存在指派对象
        }

        taskHandle.claim(taskId, userId);
        taskHandle.complete(taskId, variables);

        //获取后续任务
        TaskDTO nextTask = getTaskByProcessId(processId);
        if (nextTask == null) {
            process.setStatus(ProcessStatusEnum.COMPLETED.getStatus());
        }

        //终止
        if (action == ProcessActionEnum.SUSPEND.getValue()) {
            process.setStatus(ProcessStatusEnum.SUSPEND.getStatus());
        }

        service.updateById(process);
    }

    /**
     * 获取所有待处理流程任务集合
     * @param processDefKey 流程定义Key
     * @return 任务列表
     */
    private static List<Task> allTasks(String processDefKey) {
        return taskQueryHandle.createTaskQuery().processDefinitionKey(processDefKey).list();
    }

    /**
     * 获取当前用户待处理流程实例ID集合
     * @param processDefKey 流程定义Key
     * @return 流程实例ID集合
     */
    public static List<String> allProcessIds(String processDefKey) {
        List<Task> taskList = allTasks(processDefKey);
        if (taskList == null || taskList.size() == 0) {
            return null;
        }
        return taskList.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
    }

    /**
     * 获取当前用户待处理流程任务集合
     * @param processDefKey 流程定义Key
     * @return 任务列表
     */
    private static List<Task> userTasks(String processDefKey) {
        // 当前用户
        SysUserEntity user = ShiroUtils.getUser();

        // 当前用户任务
        return taskQueryHandle.createTaskQuery().processDefinitionKey(processDefKey).taskAssignee(String.valueOf(user.getId())).list();
    }

    /**
     * 获取当前用户待处理流程实例ID集合
     * @param processDefKey 流程定义Key
     * @return 流程实例ID集合
     */
    public static List<String> userProcessIds(String processDefKey) {
        List<Task> taskList = userTasks(processDefKey);
        if (taskList == null || taskList.size() == 0) {
            return null;
        }
        return taskList.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
    }

    /**
     * 获取当前角色待处理流程任务集合
     * @param processDefKey 流程定义Key
     * @return 任务列表
     */
    private static List<Task> groupTasks(String processDefKey) {
        //获取用户所属的角色列表
        List<Long> roleIdList = sysUserRoleService.queryRoleIdList(ShiroUtils.getUserId());

        if (roleIdList == null || roleIdList.size() == 0) {
            return null;
        }
        List<String> groups = roleIdList.stream().map(String::valueOf).collect(Collectors.toList());

        return taskQueryHandle.createTaskQuery().taskCandidateGroupIn(groups).processDefinitionKey(processDefKey).list();
    }

    /**
     * 获取当前角色待处理流程实例ID集合
     * @param processDefKey 流程定义Key
     * @return 流程实例ID集合
     */
    public static List<String> groupProcessIds(String processDefKey) {
        List<Task> taskList = groupTasks(processDefKey);
        if (taskList == null || taskList.size() == 0) {
            return null;
        }
        return Objects.requireNonNull(groupTasks(processDefKey)).stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
    }

    /**
     * 获取已处理流程任务集合
     * @param processDefKey 流程定义Key
     * @return 已处理任务列表
     */
    private static List<HistoricTaskInstance> myHistoricTasks(String processDefKey) {
        // 当前用户
        SysUserEntity user = ShiroUtils.getUser();

        return taskQueryHandle.createHistoricTaskInstanceQuery().taskAssignee(String.valueOf(user.getId())).processDefinitionKey(processDefKey).list();
    }

    /**
     * 获取当前角色已处理流程实例ID集合
     * @param processDefKey 流程定义Key
     * @return 流程实例ID集合
     */
    public static List<String> myHistoricProcessIds(String processDefKey) {
        return myHistoricTasks(processDefKey).stream().map(HistoricTaskInstance::getProcessInstanceId).collect(Collectors.toList());
    }

    /**
     * 根据任务id获取任务DTO对象
     * @param taskId 流程任务id
     * @return 任务DTO对象
     */
    private static TaskDTO getTaskById(String taskId) {
        Task task = taskQueryHandle.createTaskQuery()
                .taskId(taskId)
                .singleResult();

        if (task == null) {
            return null;
        }

        ProcessInstance pi = workFlowQueryHandle.createProcessInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();

        String businessKey = pi.getBusinessKey();

        List<Map> outFlags = findOutFlagListByTaskId(task, pi);

        TaskDTO taskDTO = new TaskDTO();
        taskDTO.setBusinessKey(businessKey);
        taskDTO.setTaskId(task.getId());
        taskDTO.setTaskName(task.getName());
        taskDTO.setProcessInstanceId(task.getProcessInstanceId());
        taskDTO.setCreateTime(task.getCreateTime());
        taskDTO.setOutFlags(outFlags);

        return taskDTO;
    }

    /**
     * 根据流程实例id获取任务DTO对象
     * @param processId 流程实例ID
     * @return 任务DTO对象
     */
    public static TaskDTO getTaskByProcessId(String processId) {
        if (StrUtil.isBlank(processId)) {
            return null;
        }
        Task task = taskQueryHandle.processInstanceId(processId);
        if (task == null) {
            return null;
        }
        return getTaskById(task.getId());
    }

    /**
     * 根据流程实例id获取任务DTO对象
     * @param process 流程对象
     */
    public static void setTask(ProcessEntity process) {
        if (process == null || ObjectUtil.isEmpty(process.getProcessId())) {
            return;
        }
        process.setTask(getTaskByProcessId(String.valueOf(process.getProcessId())));
    }

    /**
     * 根据流程实例ID查询审核历史
     * @param  processId 流程对象
     */
    public static List<TaskDTO> getHistoricByProcessId(String processId) {
        if (StrUtil.isBlank(processId)) {
            return null;
        }
        List<HistoricTaskInstance> historicTaskList = taskQueryHandle.createHistoricTaskInstanceQuery().processInstanceId(processId).list();
        if (CollectionUtil.isEmpty(historicTaskList)) {
            return null;
        }

        List<TaskDTO> taskDtoList = new ArrayList<>();
        historicTaskList.forEach(historicTask -> {
            TaskDTO taskDTO = new TaskDTO();
            taskDTO.setTaskId(historicTask.getId());
            taskDTO.setProcessInstanceId(historicTask.getProcessInstanceId());
            taskDTO.setProcessDefKey(historicTask.getProcessDefinitionId());
            taskDTO.setTaskName(historicTask.getName());
            taskDTO.setCreateTime(historicTask.getStartTime());
            taskDTO.setOperatTime(historicTask.getEndTime());
            taskDTO.setOperator(StrUtil.isBlank(historicTask.getAssignee()) ? null : Long.parseLong(historicTask.getAssignee()));
            List<Comment> comments = taskQueryHandle.getTaskComments(historicTask.getId());
            if (CollectionUtil.isNotEmpty(comments)) {
                Comment comment = comments.get(0);
                String commentDtoStr = comment.getFullMessage();
                CommentDto commentDto = new CommentDto();
                try {
                    commentDto = JSONObject.parseObject(commentDtoStr, CommentDto.class);
                } catch (Exception e) {
                    log.warn("获取流程备注出错，task_id为{}。", historicTask.getId());
                }
                taskDTO.setAction(commentDto.getAction());
                taskDTO.setActionName(commentDto.getActionName());
                taskDTO.setOpinion(commentDto.getMessage());
            }
            taskDtoList.add(taskDTO);
        });

        return taskDtoList;
    }

    /**
     * 删除流程实例
     * @param  processInsId 流程实例id
     */
    private static void deleteProcessInstance(String processInsId) {
        workFlowHandle.deleteProcessInstance(processInsId, "删除");
    }

    /**
     * 删除流程实例
     * @param  businessIdList 业务id集合
     */
    public static void deleteBatchProcessInstance(IService service, Collection<? extends Serializable> businessIdList) {
        if (CollectionUtil.isNotEmpty(businessIdList)) {
            businessIdList.forEach(id -> {
                ProcessEntity entity = (ProcessEntity) service.getById(id);
                if (entity != null && entity.getProcessId() != null) {
                    deleteProcessInstance(String.valueOf(entity.getProcessId()));
                }
            });
        }
    }

    private static List<Map> findOutFlagListByTaskId(Task task, ProcessInstance pi) {
        //查询ProcessDefinitionEntiy对象
        ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) workFlowQueryHandle.getProcessDefinition(task.getProcessDefinitionId());

        ActivityImpl activityImpl = processDefinitionEntity.findActivity(pi.getActivityId());
        //获取当前活动完成之后连线的名称
        return activityImpl.getOutgoingTransitions().stream()
                .map(pvm -> {
                    Map<String, Object> map = new HashMap<>();
                    String name = (String) pvm.getProperty("name");
                    name = StrUtil.isNotBlank(name) ? name : FLAG;
                    map.put("name", name);

                    String conditionText = (String) pvm.getProperty("conditionText");
                    if (StrUtil.isBlank(conditionText)) {
                        throw new RRException("流程配置有误：连接线上必须配置ACTION条件");
                    }
                    try {
                        String actionStr = conditionText.trim().replace("${ACTION==", "").replace("}", "");
                        int action = Integer.parseInt(actionStr);
                        map.put("action", action);
                    } catch (Exception e) {
                        throw new RRException("流程配置有误：连接线上配置条件格式不正确");
                    }
                    return map;
                }).collect(Collectors.toList());
    }
}
